import { CCObject, Color, Director, director, find, geometry, gfx, Material, Mesh, Node, primitives, Quat, renderer, utils, Vec3, VERSION, _decorator, __private } from 'cc';

const { ccclass } = _decorator;

// 临时变量
let _tempPos = new Vec3;
let _tempRotation = new Quat;
let _tempScale = new Vec3;

// 绘制信息
export interface DrawInfo {
    node?: Node,
    position?: Vec3,
    scale?: Vec3 | number,
    rotation?: Quat,
    color?: Color
}

// 绘制线段信息
export interface DrawLineInfo {
    positions: Vec3[],
    color?: Color,
}

// 材质类型
export enum MaterialType {
    Line,
    Triangle
}

/**
 * 调试绘制类
 */
@ccclass("DebugDraw")
export class DebugDraw {
    static _instance: DebugDraw;

    static get instance () {
        if (this._instance) {
            return this._instance;
        }

        this._instance = new DebugDraw();
        return this._instance;
    }

    protected _drawNode: Node | null = null;
    protected _buildId = 0;

    protected _lineMaterial: Material | null = null;
    protected _triangleMaterial: Material | null = null;

    protected _cachedMeshes: Map<string, Mesh> = new Map;
    protected _cachedGeometries: Map<string, primitives.IGeometry> = new Map();
    protected _cachedModel: Map<string, renderer.scene.Model> = new Map();

    constructor () {
        this._init();
    }

    /**
     * 初始化
     */
    private _init () {
        this._drawNode = find('DEBUG_GLOBAL_DRAW')!;
        if (!this._drawNode || !this._drawNode.isValid) {
            this._drawNode = new Node('DEBUG_GLOBAL_DRAW');
            this._drawNode._objFlags |= CCObject.Flags.DontSave | CCObject.Flags.HideInHierarchy;
            this._drawNode.parent = director.getScene() as any;
        }

        this._initMaterial();
        // 监听切换场景事件
        director.on(Director.EVENT_AFTER_SCENE_LAUNCH, this._changeScene, this);
    }

    /**
     * 初始化材质
     */
    private _initMaterial () {
        if (!this._triangleMaterial) {
            this._triangleMaterial = new Material();
            this._lineMaterial = new Material();
            this._lineMaterial._uuid = "line-material";
            this._triangleMaterial._uuid = "triangle-material";

            this._lineMaterial.initialize({
                effectName: VERSION >= "3.6.0" ? "builtin-unlit" : "unlit",
                defines: { USE_VERTEX_COLOR: true },
                states: {
                    primitive: gfx.PrimitiveMode.LINE_LIST,     // 线条
                }
            });
            this._lineMaterial.passes.forEach(v => v.tryCompile());

            this._triangleMaterial.initialize({
                effectName: VERSION >= "3.6.0" ? "builtin-unlit" : "unlit",
                defines: { USE_VERTEX_COLOR: true },
                states: {
                    primitive: gfx.PrimitiveMode.TRIANGLE_LIST, // 填充三角形
                }
            });
            this._triangleMaterial.passes.forEach(v => v.tryCompile());
        }
    }

    /**
     * 切换场景
     */
    private _changeScene () {
        this._init();
        this._initMaterial();
        this._cachedMeshes.clear();
        this._cachedGeometries.clear();
        this._cachedModel.clear();
    }

    /**
     * 获取名字
     * @param type 
     * @param materialType 
     * @returns 
     */
    _getName (type: string, materialType: MaterialType) {
        return `${type}_${materialType == 0 ? "line" : "triangle"}_${this._buildId++}`
    }

    /**
     * 绘制图形
     * @param type 类型
     * @param info 绘制信息
     * @param name 名字
     * @param materialType 材质类型 线条还是三角形
     * @returns 
     */
    public draw (type: string, info?: DrawInfo, name?: string, materialType?: MaterialType) {
        if (!this._lineMaterial) {
            return "";
        }
        materialType = materialType ? materialType : MaterialType.Line;
        name = name ? name : this._getName(type, materialType);
        let model = this._getModel(name);
        let mesh = this._getMesh(type, info ? info.color : undefined);
        mesh && model.initSubModel(0, mesh.renderingSubMeshes[0], materialType == MaterialType.Line ? this._lineMaterial! : this._triangleMaterial!);
        if (info) {
            if (info.node) {
                info.node.getWorldPosition(_tempPos);
                info.node.getWorldRotation(_tempRotation);
                info.node.getWorldScale(_tempScale);

                model.node.setRTS(_tempRotation, _tempPos, _tempScale);
            } else {
                Vec3.copy(_tempScale, Vec3.ONE);
                if (info.scale) {
                    if (info.scale instanceof Vec3) {
                        Vec3.copy(_tempScale, info.scale);
                    } else if (typeof info.scale == "number") {
                        _tempScale.multiplyScalar(info.scale);
                    }
                }
                model.node.setRTS(info.rotation ? info.rotation : Quat.IDENTITY, info.position ? info.position : Vec3.ZERO, _tempScale);
            }
        }

        return name;
    }

    /**
     * 绘制box
     * @param info  绘制信息
     * @param materialType  材质类型
     * @param name  指定名称 
     * @returns 
     */
    public box (info: DrawInfo = {}, materialType: MaterialType = MaterialType.Triangle, name?: string) {
        let type = "box"
        return this.draw(type, info, name, materialType);
    }

    /**
     * 绘制aabb
     * @param aabb 
     * @param color 
     * @param materialType 
     * @param name 
     * @returns 
     */
    public aabb (aabb: geometry.AABB, color = Color.RED, name?: string) {
        let type = "box";
        let info: DrawInfo = {};
        info.position = aabb.center;
        info.scale = new Vec3;
        Vec3.multiplyScalar(info.scale, aabb.halfExtents, 2);
        info.color = color.clone();
        return this.draw(type, info, name, MaterialType.Line);
    }

    /**
     * 绘制line
     * @param info  绘制信息
     * @param name  指定名称 
     * @returns 
     */
    public line (info: DrawLineInfo, name?: string) {
        let type = "line"

        name = name ? name : this._getName(type, MaterialType.Line);
        let model = this._getModel(name);
        let mesh = this._cachedMeshes.get(name);
        if (mesh) {
            mesh.destroy();
        }
        // if (!mesh) {
        let g = this._line(info.positions);

        let positions = g.positions;
        let colors = [];
        let color = info.color ? info.color : Color.BLUE;
        for (let i = 0; i < positions.length; i += 3) {
            colors.push(color.r);
            colors.push(color.g);
            colors.push(color.b);
            colors.push(color.a);
        }
        g.colors = colors;

        mesh = utils.createMesh(g);
        this._cachedMeshes.set(name, mesh);
        //  }
        mesh && model.initSubModel(0, mesh.renderingSubMeshes[0], this._lineMaterial!);

        return name;
    }

    /**
     * 绘制ray
     * @param info  绘制信息
     * @param name  指定名称 
     * @returns 
     */

    public ray (info: { ray: geometry.Ray, color?: any }, name?: string) {
        let lineInfo: any = { positions: [], color: info.color };
        Vec3.scaleAndAdd(_tempPos, info.ray.o, info.ray.d, 1000);
        lineInfo.positions.push(info.ray.o);
        lineInfo.positions.push(_tempPos);

        return this.line(lineInfo, name);
    }

    /**
     * 绘制sphere
     * @param info  绘制信息
     * @param materialType  材质类型
     * @param name  指定名称 
     * @returns 
     */
    public sphere (info?: DrawInfo, materialType: number = 0, name?: string) {
        let type = "sphere"
        return this.draw(type, info, name, materialType);

    }

    /**
     * 绘制节点的模型
     * @param name 
     * @returns 
     */
    private _getModel (name: string) {
        let model = this._cachedModel.get(name);
        if (model) {
            return model;
        }
        model = director.root!.createModel(renderer.scene.Model);
        model.enabled = true;
        model.node = model.transform = new Node; // this.node 可以作用position 以及scale
        model.node._objFlags |= CCObject.Flags.DontSave | CCObject.Flags.HideInHierarchy;
        model.node.parent = this._drawNode;

        this._cachedModel.set(name, model);

        model.node.scene.renderScene!.addModel(model);

        return model;
    }

    /**
     * 绘制节点的蒙皮
     * @param name 
     * @param color 
     * @returns 
     */
    private _getMesh (name: string, color = Color.BLUE.clone()) {
        let mesh = this._cachedMeshes.get(name);
        if (!mesh) {
            let g = this._getGeometry(name);
            let positions = g.positions;
            let colors = [];
            for (let i = 0; i < positions.length; i += 3) {
                colors.push(color.r);
                colors.push(color.g);
                colors.push(color.b);
                colors.push(color.a);
            }
            g.colors = colors;

            mesh = utils.createMesh(g);
            this._cachedMeshes.set(name, mesh);
        }
        return mesh;
    }

    /**
     *  获取绘制线条的geometry
     * @param args 
     * @returns 
     */
    private _line (...args: Vec3[][]): primitives.IGeometry {
        let positions: number[] = []
        let indices: number[] = []

        let pointIndex = 0;
        args.forEach(points => {
            let startIndex = pointIndex;
            for (let i = 0; i < points.length; i++) {
                positions.push(points[i].x, points[i].y, points[i].z);
                if ((i + 1) < points.length) {
                    indices.push(pointIndex, pointIndex + 1);
                }
                else {
                    indices.push(pointIndex, startIndex);
                }
                pointIndex++;
            }
        })

        return {
            positions,
            indices,
        }
    }

    /**
     *  获取绘制模型的geometry
     * @param args 
     * @returns 
     */
    private _getGeometry (name: string) {
        let g = this._cachedGeometries.get(name)!;
        if (!g) {
            let func = (primitives as any)[name];
            if (!func) {
                console.warn(`Can not find primitives.${name}`);
                func = primitives.box;
            }

            if (name === 'sphere') {
                g = primitives.sphere(1)
            }
            else {
                g = func()
            }
            this._cachedGeometries.set(name, g);
        }
        return g;
    }

    /**
     * 清除屏幕上绘制的内容
     */
    public clear (name = "") {
        if (name) {
            let model = this._cachedModel.get(name);
            if (model) {
                model.node.removeFromParent();
                model.node.scene.renderScene!.removeModel(model);
                model.enabled = false;
                this._cachedModel.delete(name)
            }
        } else {
            this._cachedModel.forEach((model, key) => {
                model.node.removeFromParent();
                model.node.scene.renderScene!.removeModel(model);
                model.enabled = false;
            });
            this._cachedModel.clear();
        }
    }
}


